package jrain.fw.business.common;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

import jrain.fw.core.business.Business;
import jrain.fw.core.business.BusinessConst;
import jrain.fw.core.business.BusinessInfo;
import jrain.fw.core.business.BusinessMethod;
import jrain.fw.core.business.BusinessProvider;
import jrain.fw.core.business.BusinessRequest;
import jrain.fw.core.business.BusinessResponse;
import jrain.fw.core.log.Logger;
import jrain.fw.core.log.LoggerFactory;
import jrain.fw.core.service.DestroyService;
import jrain.fw.core.service.InitService;
import jrain.fw.core.service.Service;
import jrain.fw.core.service.ServiceCfg;
import jrain.fw.core.service.ServiceConst;
import jrain.fw.core.utils.FwUtils;
import jrain.utils.cls.ClassUtils;
import jrain.utils.collection.CollectionUtils;
import jrain.utils.exception.BusinessException;
import jrain.utils.exception.ServiceException;
import jrain.utils.lang.StringUtils;

@Service(group = ServiceConst.S_BUSINESS_PROVIDER, module = ServiceConst.M_BUSINESS)
public class BusinessProviderService implements BusinessProvider, InitService, DestroyService {

	private static final Logger log = LoggerFactory.getLogger("business-provider");

	private static final Map<String, BusinessBean> businessMap = new HashMap<>();
	private static final List<BusinessInfo> businessInfos = new ArrayList<>();
	private static final AtomicBoolean init = new AtomicBoolean(false);

	@Override
	public BusinessResponse doSync(BusinessRequest request) {
		BusinessResponse businessResponse = new BusinessResponse();
		String key = request.getKey();
		if (init.get()) {
			BusinessBean businessBean = businessMap.get(key);
			if (businessBean != null) {
				try {
					businessResponse.setCode(BusinessConst.RES_CODE_ERROR_BUIS_NOTFIND);
					businessResponse = (BusinessResponse) businessBean.getMethod().invoke(businessBean.getObj(), request);
				} catch (BusinessException e) {
					businessResponse.setCode(e.getCode());
					businessResponse.setMsg(e.getMessage());
					log.error(key, e);
				} catch (ServiceException e) {
					businessResponse.setCode(e.getCode());
					businessResponse.setMsg(e.getMessage());
					log.error(key, e);
				} catch (Throwable t) {
					businessResponse.setCode(BusinessConst.RES_CODE_ERROR_UNKOWN);
					businessResponse.setMsg(t.getMessage());
					log.error(key, t);
				}
			} else {
				
				businessResponse.setCode(BusinessConst.RES_CODE_ERROR_BUIS_NOTFIND);
				log.warn("{} business not found!", key);
			}
			return businessResponse;
		} else {
			log.warn("{} business not found!,service not initialized!", key);
			businessResponse.setCode(BusinessConst.RES_CODE_ERROR_BUIS_NOTFIND);
			return businessResponse;
		}
	}

	@Override
	public void doAsync(BusinessRequest request, Consumer<BusinessResponse> callbak) {
		BusinessResponse businessResponse = new BusinessResponse();
		String key = request.getKey();
		if (init.get()) {
			BusinessBean businessBean = businessMap.get(key);
			if (businessBean != null) {
				try {
					businessResponse.setCode(BusinessConst.RES_CODE_ERROR_BUIS_NOTFIND);
					businessBean.getMethod().invoke(businessBean.getObj(), request, callbak);
				} catch (BusinessException e) {
					businessResponse.setCode(e.getCode());
					businessResponse.setMsg(e.getMessage());
					log.error(key, e);
					callbak.accept(businessResponse);
				} catch (ServiceException e) {
					businessResponse.setCode(e.getCode());
					businessResponse.setMsg(e.getMessage());
					log.error(key, e);
					callbak.accept(businessResponse);
				} catch (Throwable t) {
					businessResponse.setCode(BusinessConst.RES_CODE_ERROR_UNKOWN);
					businessResponse.setMsg(t.getMessage());
					log.error(key, t);
					callbak.accept(businessResponse);
				}
			} else {
				businessResponse.setCode(BusinessConst.RES_CODE_ERROR_BUIS_NOTFIND);
				log.warn("{} business not found!", key);
				callbak.accept(businessResponse);
			}
		} else {
			log.warn("{} business not found!,service not initialized!", key);
			businessResponse.setCode(BusinessConst.RES_CODE_ERROR_BUIS_NOTFIND);
			callbak.accept(businessResponse);
		}
	}

	@Override
	public List<BusinessInfo> getBusinessInfos() {
		return businessInfos;
	}

	@Override
	public void init() {
		if (!init.getAndSet(true)) {
			try {
				String pkgs = StringUtils.trimNull(FwUtils.getFwCfg().get(ServiceCfg.businessScan.getName()), ServiceCfg.businessScan.getValue());
				List<String> pkgList = CollectionUtils.valueOfList(pkgs);
				log.info("business scan pkgs {}", pkgs);
				for (String string : pkgList) {
					Set<Class<?>> list = ClassUtils.getClasses(string);
					for (Class<?> claz : list) {
						Business service = claz.getAnnotation(Business.class);
						if (service != null) {
							Method[] methods = claz.getDeclaredMethods();
							for (Method method : methods) {
								BusinessMethod businessMethod = method.getAnnotation(BusinessMethod.class);
								if (businessMethod != null) {
									String key = service.name() + "." + method.getName() + "." + businessMethod.version();
									log.info("business  name:{} class:{}", key, claz.getName());
									businessMap.put(key, new BusinessBean(claz.newInstance(), method));
									BusinessInfo bean = new BusinessInfo();
									bean.setService(service.name());
									bean.setMethod(method.getName());
									bean.setService(businessMethod.version());
									bean.setRetry(businessMethod.retry());
									businessInfos.add(bean);
								}
							}

						}
					}
				}
				log.info("{} started!", ServiceConst.S_BUSINESS_PROVIDER);
			} catch (Exception e) {
				String msg = ServiceConst.S_BUSINESS_PROVIDER + "start error!";
				log.error(msg, e);
				throw new ServiceException(msg, e);
			}
		}
	}

	public void destroy() {
		if (init.get()) {
			businessMap.clear();
			businessInfos.clear();
			log.info("{} stoped!", ServiceConst.S_BUSINESS_PROVIDER);
			init.set(false);
		}
	}

}
